package hc.server.ui.design.hpj;

import hc.server.ui.HCByteArrayOutputStream;
import hc.server.ui.design.Designer;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.script.ScriptException;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.JTextPane;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.DefaultHighlighter;
import javax.swing.text.Highlighter;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyleContext;
import javax.swing.text.StyledDocument;
import javax.swing.text.TabSet;
import javax.swing.text.TabStop;
import javax.swing.tree.MutableTreeNode;

public abstract class ScriptEditPanel extends NodeEditPanel {
	static Highlighter.HighlightPainter ERROR_CODE_LINE_LIGHTER = new DefaultHighlighter.
			DefaultHighlightPainter(Color.RED);
	
	private static final SimpleAttributeSet STR_LIGHTER = build(Color.decode("#4EA539"), false);
	private static final SimpleAttributeSet REM_LIGHTER = build(Color.decode("#3AC2EB"), false);
	private static final SimpleAttributeSet MAP_LIGHTER = build(Color.BLACK, true);
	private static final SimpleAttributeSet KEYWORDS_LIGHTER = build(Color.BLUE, true);
	private static final SimpleAttributeSet NUM_LIGHTER = build(Color.RED, false);
	private static final SimpleAttributeSet DEFAULT_LIGHTER = build(Color.BLACK, false);
	
	private static final Pattern str_pattern = Pattern.compile("\".*?(?<!\\\\)\"");
	private static final Pattern keywords_pattern = Pattern.compile("\\b(BEGIN|END|__ENCODING__|__END__|__FILE__|__LINE__|alias|" +
			"and|begin|break|case|class|def|defined?|do|else|elsif|end|" +
			"ensure|false|for|if|in|module|next|nil|not|or|redo|rescue|retry|return|" +
			"self|super|then|true|undef|unless|until|when|while|yield)\\b", Pattern.MULTILINE);
	private static final Pattern num_pattern = Pattern.compile("\\b\\d+\\b", Pattern.MULTILINE);
	private static final Pattern hc_map_pattern = Pattern.compile("\\$_hcmap\\b");
	private static final Pattern rem_pattern = Pattern.compile("#(.*)(?=$|\n)", Pattern.MULTILINE);//(?:^|\r|\n)

	HPNode currItem;
	final JScrollPane scrollpane;
	final JLabel errRunInfo = new JLabel("");
	final JButton testBtn = new JButton("Test Script");
	final HCByteArrayOutputStream iconByteArrayos = new HCByteArrayOutputStream();
	final JTextField nameField = new JTextField();

	boolean isInited = false;
	
	Map<String, String> buildMapScriptParameter(){
		return null;
	}
	
	public ScriptEditPanel() {
		errRunInfo.setForeground(Color.RED);

		testBtn.setIcon(Designer.loadImg("hc/server/ui/design/res/test_16.png"));
		
		testBtn.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				Map<String, String> map = buildMapScriptParameter();
				if(RubyExector.getEvalException() != null){
					//清空旧错误
					errRunInfo.setText("");
					jtaScript.getHighlighter().removeAllHighlights();
				}
				RubyExector.run(jtaScript.getText(), map);
				final ScriptException evalException = RubyExector.getEvalException();
				if(evalException != null){
//					final Object runnable = Toolkit.getDefaultToolkit().getDesktopProperty("win.sound.exclamation");
//					 if (runnable != null && runnable instanceof Runnable){
//						 ((Runnable)runnable).run();
//					 }
					 
					Toolkit.getDefaultToolkit().beep();
					
					errRunInfo.setText(evalException.getMessage());
					final int line = evalException.getLineNumber();
					char[] chars = jtaScript.getText().toCharArray();
					
					int currRow = 1;
					int startIdx = -1, endIdx = -1;
					if(line == 1){
						startIdx = 0;
					}
					for (int i = 0; i < chars.length; i++) {
						if(chars[i] == '\n'){
							if(++currRow == line && (startIdx == -1)){
								startIdx = i + 1;
							}else if(startIdx >=0 && endIdx == -1){
								endIdx = i;
								break;
							}
							
						}
					}
					if(startIdx >= 0){
						if(endIdx < 0){
							endIdx = chars.length;
						}
						try {
							jtaScript.getHighlighter().addHighlight(startIdx, endIdx, ERROR_CODE_LINE_LIGHTER);
						} catch (BadLocationException e1) {
						}
						jtaScript.setCaretPosition(startIdx);
					}
				}
			}
		});

		//		jtaScript.setTabSize(2);
		
		//以下代码设置Tab跳过指定的空格
		FontMetrics fm = jtaScript.getFontMetrics(jtaScript.getFont());
		int cw = fm.stringWidth("    ");
		float f = (float)cw;
		TabStop[] tabs = new TabStop[10]; // this sucks
		for(int i = 0; i < tabs.length; i++){
			tabs[i] = new TabStop(f * (i + 1), TabStop.ALIGN_LEFT, TabStop.LEAD_NONE);
		}
		TabSet tabset = new TabSet(tabs);
		StyleContext sc = StyleContext.getDefaultStyleContext();
		AttributeSet aset = sc.addAttribute(SimpleAttributeSet.EMPTY, StyleConstants.TabSet, tabset);
		jtaScript.setParagraphAttributes(aset, false);	
		
		jtaScript.addKeyListener(new KeyListener() {
			@Override
			public void keyTyped(KeyEvent e) {
				if(e.isControlDown() || e.isAltDown()){
					return;
				}
				SwingUtilities.invokeLater(run);
			}
			
			@Override
			public void keyReleased(KeyEvent e) {
			}
			
			@Override
			public void keyPressed(KeyEvent e) {
			}
		});
//		jtaScript.getDocument().addDocumentListener(new DocumentListener() {
//			final Runnable run = new Runnable() {
//				@Override
//				public void run() {
//					initColor();
//					notifyModified();
//				}
//			};
//			@Override
//			public void removeUpdate(DocumentEvent e) {
//				System.out.println(e.toString());
//				SwingUtilities.invokeLater(run);
//			}
//			@Override
//			public void insertUpdate(DocumentEvent e) {
//				System.out.println(e.toString());
//				SwingUtilities.invokeLater(run);
//			}
//			@Override
//			public void changedUpdate(DocumentEvent e) {
//			}
//		});
		LineNumber lineNumber = new LineNumber(jtaScript);
		scrollpane = new JScrollPane(jtaScript);
		scrollpane.setRowHeaderView(lineNumber);

		nameField.getDocument().addDocumentListener(new DocumentListener() {
			private void modify(){
				currItem.name = nameField.getText();
				notifyModified();
			}
			@Override
			public void removeUpdate(DocumentEvent e) {
				modify();
			}
			
			@Override
			public void insertUpdate(DocumentEvent e) {
				modify();
			}
			
			@Override
			public void changedUpdate(DocumentEvent e) {
				modify();
			}
		});
		nameField.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				currItem.name = nameField.getText();
				tree.updateUI();
				notifyModified();
			}
		});

	}
	
	public abstract void updateScript(String script);
	
	void notifyModified(){
		if(isInited){
			currItem.getContext().modified.setModified(true);
		}
	}
	
	public void init(MutableTreeNode data, JTree tree) {
		super.init(data, tree);
		
		isInited = false;
		
		jtaScript.setText("");
		errRunInfo.setText("");

		currItem = (HPNode)currNode.getUserObject();
		nameField.setText(currItem.name);
	}
	
	JTextPane jtaScript = new JTextPane(){
		public void paste(){
			super.paste();
			SwingUtilities.invokeLater(run);
		}
	};
	final Runnable run = new Runnable() {
		@Override
		public void run() {
			initColor();
			notifyModified();
			updateScript(jtaScript.getText());
		}
	};
	void initColor(){
		final String text = jtaScript.getText();
		if(text.indexOf("\r") >= 0){
			jtaScript.setText(text.replace("\r", ""));
		}
		
		final StyledDocument document = (StyledDocument)jtaScript.getDocument();
		document.setCharacterAttributes(0, document.getLength(), DEFAULT_LIGHTER, true);
		
		buildHighlight(jtaScript, hc_map_pattern, MAP_LIGHTER);
		buildHighlight(jtaScript, num_pattern, NUM_LIGHTER);//要置于字符串之前，因为字符串中可能含有数字
		buildHighlight(jtaScript, keywords_pattern, KEYWORDS_LIGHTER);
		buildHighlight(jtaScript, rem_pattern, REM_LIGHTER);//字符串中含有#{}，所以要置于STR_LIGHTER之前
		buildHighlight(jtaScript, str_pattern, STR_LIGHTER);//?<!\\\"
		
		try {
			if(text.length() > 0 && text.charAt(0) == '#'){
				int end = jtaScript.getDocument().getLength();
				String s = jtaScript.getText(0, (end>2000?2000:end));
				char[] chars = s.toCharArray();
				for (int i = 0; i < chars.length; i++) {
					final char c = chars[i];
					if(c == '\r' || c == '\n'){
						((StyledDocument)jtaScript.getDocument()).setCharacterAttributes(0, i, REM_LIGHTER, false);
						break;
					}
				}
			}
		} catch (BadLocationException e) {
		}
//		buildHighlight(jtaScript, first_rem_pattern, REM_LIGHTER);
	}
	
	private void buildHighlight(JTextPane jta, Pattern pattern, SimpleAttributeSet attributes) {
		final StyledDocument document = (StyledDocument)jta.getDocument();
		final Matcher matcher = pattern.matcher(jta.getText());
		boolean matchFound = matcher.matches(); // false
		if (!matchFound) {
			while (matcher.find()) {
				int start = matcher.start();
				int end = matcher.end();
//				try {
//					Font font = new Font("Verdana", Font.BOLD, 40);
//				System.out.println("match : " + start + ", end:" + end);
					 document.setCharacterAttributes(start, end - start, attributes, false);
//					jta.getHighlighter().addHighlight(start, end, lighter);
//				} catch (BadLocationException e1) {
//					e1.printStackTrace();
//				}
			}
		}
	}

	private static SimpleAttributeSet build(Color c, boolean bold){
		 SimpleAttributeSet attributes = new SimpleAttributeSet();
		 StyleConstants.setForeground(attributes, c);
		 StyleConstants.setBold(attributes, bold);
//		 StyleConstants.setFontSize(attributes, fontSize);
//		 StyleConstants.setFontFamily(attrSet, "黑体");
		 return attributes;
	}
	

}
class LineNumber extends JComponent {
	private final static Font DEFAULT_FONT = new Font("monospaced", Font.PLAIN,
			12);
	private final static int HEIGHT = Integer.MAX_VALUE - 1000000;
	// Set right/left margin
	private int lineHeight;
	private int fontLineHeight;
	private int currentRowWidth;
	private FontMetrics fontMetrics;

	/**
	 * Convenience constructor for Text Components
	 */
	public LineNumber(JComponent component) {
		if (component == null) {
			setFont(DEFAULT_FONT);
		} else {
			setFont(component.getFont());
		}
		setPreferredSize(99);
	}

	public void setPreferredSize(int row) {
		int width = fontMetrics.stringWidth(String.valueOf(row));
		if (currentRowWidth < width) {
			currentRowWidth = width;
			setPreferredSize(new Dimension(width, HEIGHT));
		}
	}

	public void setFont(Font font) {
		super.setFont(font);
		fontMetrics = getFontMetrics(getFont());
		fontLineHeight = fontMetrics.getHeight();
	}

	public int getLineHeight() {
		if (lineHeight == 0)
			return fontLineHeight;
		else
			return lineHeight;
	}

	public void setLineHeight(int lineHeight) {
		if (lineHeight > 0)
			this.lineHeight = lineHeight;
	}

	public int getStartOffset() {
		return 2;
	}

	public void paintComponent(Graphics g) {
		int lineHeight = getLineHeight();
		int startOffset = getStartOffset();
		Rectangle drawHere = g.getClipBounds();
		// System.out.println( drawHere );
		// Paint the background
		g.setColor(getBackground());
		g.fillRect(drawHere.x, drawHere.y, drawHere.width, drawHere.height);
		// Determine the number of lines to draw in the foreground.
		g.setColor(getForeground());
		int startLineNumber = (drawHere.y / lineHeight) + 1;
		int endLineNumber = startLineNumber + (drawHere.height / lineHeight);
		int start = (drawHere.y / lineHeight) * lineHeight + lineHeight
				- startOffset;
		// System.out.println( startLineNumber + " : " + endLineNumber + " : " +
		// start );
		for (int i = startLineNumber; i <= endLineNumber; i++) {
			String lineNumber = String.valueOf(i);
			int width = fontMetrics.stringWidth(lineNumber);
			g.drawString(lineNumber, currentRowWidth - width, start);
			start += lineHeight;
		}
		setPreferredSize(endLineNumber);
	}
}
